package com.sdy.dataadapter;

import com.alibaba.druid.filter.Filter;
import com.alibaba.druid.pool.DruidDataSource;
import com.sdy.dataadapter.annotation.TableFieldAdapter;
import com.sdy.dataadapter.util.FieldUtil;
import com.zaxxer.hikari.HikariDataSource;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.support.TransactionTemplate;

import javax.sql.DataSource;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.math.BigDecimal;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;

@Data
@Slf4j
public class DataAdapter extends JdbcTemplate {
    private RawDataSource rawDataSource;
    private DataSourceManager.DataSourceEx dataSourceEx;
    private TransactionTemplate transactionTemplate;
    private PlatformTransactionManager platformTransactionManager;
    
    public DataAdapter(RawDataSource rawDataSource) {
        this.rawDataSource = rawDataSource;
        dataSourceEx = DataSourceManager.getDataSource(this.rawDataSource);
        setDataSource(dataSourceEx.getDataSource());
    }

    /**
     * 检查连接是否有效
     * @return
     */
    public boolean checkConnection(long maxWaitMillis) {
        boolean r = true;
        try {
//            ((DruidDataSource) getDataSource()).getConnection(maxWaitMillis);
            HikariDataSource ds = ((HikariDataSource) getDataSource());
            ds.setConnectionTimeout(maxWaitMillis);
            ds.getConnection();
        } catch (Exception e) {
            r = false;
            log.warn("Error connection!{}", e.getMessage());
        }
        return r;
    }

    /**
     * 关闭连接
     */
    public void endConnection() {
        DataSource ds = this.dataSourceEx.getDataSource();
        if (ds != null) {
            try {
                if (!ds.getConnection().getAutoCommit()) {
                    ds.getConnection().commit();
                }
                ds.getConnection().close();
            } catch (SQLException e) {
                log.error("Connection close failed!", e);
            }
        }
    }

    /**
     * 设置最大连接数
     */
    public void setMaxConnection(int count) {
        ((HikariDataSource) Objects.requireNonNull(getDataSource())).setMaximumPoolSize(count);
    }

    /**
     * 设置filters
     */
    public void setDruidFilters(List<Filter> filters) {
        ((DruidDataSource) Objects.requireNonNull(getDataSource())).setProxyFilters(filters);
    }

    /**
     * 开启事务
     * @throws Exception 异常时回滚
     */
    public <T> T doTransaction(TransactionProc<T> proc) {
        return dataSourceEx.getTransactionTemplate().execute(status -> {
            T result;
            try {
                result = proc.apply();
            } catch (Exception e) {
                log.info("Transaction rollback for exception!", e);
                status.setRollbackOnly();
                throw new RuntimeException(e);
            }
            return result;
        });
    }

    /**
     * 执行SQL
     * @param sql SQL
     * @return 更新数量
     */
    public int executeUpdate(String sql) {
        return update(sql);
    }

    /**
     * 执行查询SQL
     */
    public <T> List<T> executeQuery(String sql, Class<T> clazz) throws IllegalAccessException, InstantiationException {
        List<Map<String, Object>> list = executeQuery(sql);
        return parseObjectList(list, clazz);
    }

    /**
     * 执行查询SQL
     */
    public <T> List<T> executeQuery(String sql, Object[] args, Class<T> clazz) throws IllegalAccessException, InstantiationException {
        List<Map<String, Object>> list = executeQuery(sql, args);
        return parseObjectList(list, clazz);
    }

    /**
     * 执行查询SQL，返回HashMap列表
     */
    public List<Map<String, Object>> executeQuery(String sql) {
        return queryForList(sql);
    }

    /**
     * 返回count
     */
    public long count(String sql) {
        List<Map<String, Object>> maps = this.executeQuery(sql);
        Object obj = maps.get(0).values().iterator().next();
        if (obj instanceof Long) {
            return (long) obj;
        } else if (obj instanceof BigDecimal) {
            return ((BigDecimal) obj).longValue();
        } else {
            return Long.valueOf(obj.toString());
        }
    }

    /**
     * 执行查询SQL，返回HashMap列表
     */
    public List<Map<String, Object>> executeQuery(String sql, Object[] args) {
        return queryForList(sql, args);
    }

    private <T> List<T> parseObjectList(List<Map<String, Object>> list, Class<T> clazz) throws IllegalAccessException, InstantiationException {
        List<T> resultList = new ArrayList<>();
        Field[] fields = clazz.getDeclaredFields();
        boolean upperCase = rawDataSource.getDbType().equals(DbType.ORACLE);
        for (Map<String, Object> rsMap : list) {
            T obj = clazz.newInstance();
            for (Field item : fields) {
                TableFieldAdapter anno1 = item.getAnnotation(TableFieldAdapter.class);
                if (!Modifier.isStatic(item.getModifiers())) {
                    if (anno1 != null && !anno1.exist()) {
                        continue;
                    }
                    String columnName;
                    if (anno1 == null || "".equals(anno1.field())) {
                        columnName = FieldUtil.underscoreName(item.getName(), upperCase);
                    } else {
                        columnName = anno1.field();
                    }
                    item.setAccessible(true);
                    Object value = rsMap.get(columnName);
                    if (value instanceof BigDecimal) {
                        if (item.getType() == Integer.class) {
                            value = ((BigDecimal) value).intValue();
                        } else if (item.getType() == Long.class) {
                            value = ((BigDecimal) value).longValue();
                        } else if (item.getType() == Short.class) {
                            value = ((BigDecimal) value).shortValue();
                        }
                    }
                    item.set(obj, value);
                }
            }
            resultList.add(obj);
        }
        return resultList;
    }
}
